<?php

/**
 * Description of User
 *
 * @author mie
 */
class Model_User extends Core_Model {
    /**
     * Salt used in calculating password-hash
     */
    const PASS_SALT = 'suola';
    private $insertQuery = 'INSERT INTO User
        (username, password, email, userStatusId, userGroupId) VALUES
        ( {username}, md5({password}), {email}, {userStatusId}, {userGroupId})';

    private $updateQueryWithPassword = 'UPDATE User SET
                username = {username}, password = md5({password}),
                email = {email}, userStatusId = {userStatusId},
                userGroupId = {userGroupId}
            WHERE id = {id} LIMIT 1';
    private $updateQueryWithoutPassword = 'UPDATE User SET
                username = {username}, email = {email},
                userStatusId = {userStatusId}, userGroupId = {userGroupId}
            WHERE id = {id} LIMIT 1';

    private $id;
    private $username;
    private $email;
    private $userStatusId;
    private $userGroupId;
    private $password;
    private $password2;
    private $insertRules = array
        (
        'username' => array('type' => Core_Database::Type_String),
        'password' => array('type' => Core_Database::Type_String),
        'email' => array('type' => Core_Database::Type_String),
        'userStatusId' => array('type' => Core_Database::Type_Int, 'minValue' => 1),
        'userGroupId' => array('type' => Core_Database::Type_Int, 'minValue' => 1)
    );
    private $updateRules = array
        (
        'id' => array('type' => Core_Database::Type_Int, 'minValue' => 1),
        'username' => array('type' => Core_Database::Type_String),
        'password' => array('type' => Core_Database::Type_String),
        'email' => array('type' => Core_Database::Type_String),
        'userStatusId' => array('type' => Core_Database::Type_Int, 'minValue' => 1),
        'userGroupId' => array('type' => Core_Database::Type_Int, 'minValue' => 1)
    );

    public function __construct($properties) {
        $className = get_class($this);
        foreach ($properties as $name => $value) {
            if (property_exists($className, $name))
                $this->$name = $value;
        }
    }

    /**
     * Login as an existing user.
     *
     * @param $username
     * @param $password
     * @return Model_User on success, nothing on failure.
     * @throws Core_Database_Exception on SQL error
     */
    public static function login($username, $password) {
        Core_Logger::log("loggin in: $username, " . str_repeat('x', strlen($password)));
        $db = self::getDatabaseHandler();
        $rules = array
            (
            'username' => array('type' => Core_Database::Type_String),
            'password' => array('type' => Core_Database::Type_String)
        );
        $db->setRules($rules);
        $db->prepare('select id, username from User where username = {username} and password = md5({password})');
        $db->setParameter('username', $username);
        $password = self::formPassString($username, $password);
        $db->setParameter('password', $password);
        $db->execute();

        if ($db->fetch()) {
            Core_Logger::log("$username logged in");
            return self::loginSuccess($db->getResult());
        } else {
            Core_Logger::log("$username failed to log in");
        }
    }

    /**
     * After succesful login
     *
     * @param $properties Logged user array
     * @return Model_User
     */
    private static function loginSuccess($properties) {
        return new Model_User($properties);
    }

    private static function formPassString($username, $password) {
        return $username . $password . self::PASS_SALT;
    }

    /**
     * getter for Id
     */
    public function getId() {
        return $this->id;
    }

    /**
     * Save user to Database
     * @return $user User information in array (id => #, username => '', ...)
     * @throws Model_User_Exception on error
     */
    public function save() {
        if ($this->id)
            $this->update();
        else
            $this->create();
    }

    /**
     * Rerturn user information on PHP array
     * @return $user User information in array(
     *      id => this->id,
     *      username => this->username,
     *      email => this->email,
     *      userGroupId => this->userGroupId,
     *      userStatusId => this->userStatusId,
     *      ...
     * )
     */
    public function serializeToArray() {
        return array(
            'id' => $this->id,
            'username' => $this->username,
            'email' => $this->email,
            'userGroupId' => $this->userGroupId,
            'userStatusId' => $this->userStatusId,
            'password' => '',
            'password2' => ''
        );
    }

    /**
     * Create new User to system, username and password must be set
     * @throws Model_User_Exception on error
     */
    private function create() {
        try {
            $this->validateInsert();
            $db = self::getDatabaseHandler();
            $this->prepareInsert($db);
            $db->execute();
            $this->id = $db->insertId();
        } catch (Model_User_Exception $e) {
            throw new Model_User_Exception('Unable to create user: ' . $e->getMessage());
        }
    }

    /**
     * Update existing User to system
     * If password and password2 are set, password is also updated
     * @throws Model_User_Exception on error
     */
    private function update() {
        try {
            $this->validateUpdate();
            $db = self::getDatabaseHandler();
            $this->prepareUpdate($db);
            $db->execute();
        } catch (Model_User_Exception $e) {
            throw new Model_User_Exception('Unable to update user: ' . $e->getMessage());
        }
    }

    /**
     * Validate that password are equal and meet complexity requirements
     * @param $pw1 Password 1
     * @param $pw2 Password 2
     * @throws Model_User_Exception if passwords are invalid
     */
    public function validatePasswords($pw1, $pw2) {
        if ($pw1 !== $pw2)
            throw new Model_User_Exception('Passwords mismatch');
        else if (!preg_match("/^.*(?=.{8,})(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).*$/", $pw1))
            throw new Model_User_Exception(
                    'Weak password, must be 8 characters with a capital and small letter and digit');
    }

    /**
     * Prepare the database query and bind variables for creaging new user
     * @param $db Database handler as reference
     */
    private function prepareUpdate(&$db) {
        $db->setRules($this->updateRules);
        $updatePW = strlen($this->password) > 1;
        if ($updatePW)
            $db->prepare($this->updateQueryWithPassword);
        else
            $db->prepare($this->updateQueryWithoutPassword);

        $db->setParameter('username', $this->username);
        $this->password2 = self::formPassString($this->username, $this->password);
        if ($updatePW)
            $db->setParameter('password', $this->password2);
        $db->setParameter('email', $this->email);
        $db->setParameter('userStatusId', $this->userStatusId);
        $db->setParameter('userGroupId', $this->userGroupId);
        $db->setParameter('id', $this->id);
    }

    /**
     * Prepare the database query and bind variables
     * @param $db Database handler as reference 
     */
    private function prepareInsert(&$db) {
        $db->setRules($this->insertRules);
        $db->prepare($this->insertQuery);
        $db->setParameter('username', $this->username);
        $this->password2 = self::formPassString($this->username, $this->password);
        $db->setParameter('password', $this->password2);
        $db->setParameter('email', $this->email);
        $db->setParameter('userStatusId', $this->userStatusId);
        $db->setParameter('userGroupId', $this->userGroupId);
    }

    /**
     * Check that object has all variables set in order to update
     * If password is given it is validated against password2
     * @throws Model_User_Exception
     */
    private function validateUpdate() {
        if (!$this->id)
            throw new Model_User_Exception('Missing ID');
        if (!$this->username)
            throw new Model_User_Exception('Missing username');
        if (!$this->email)
            throw new Model_User_Exception('Missing email');
        if (strlen($this->password) > 1) {
            $this->validatePasswords($this->password, $this->password2);
        }
    }

    /**
     * Check that object has all variables set in order to insert
     * @throws Model_User_Exception
     */
    private function validateInsert() {
        if (!$this->username)
            throw new Model_User_Exception('Missing username');
        if (!$this->email)
            throw new Model_User_Exception('Missing email');
        $this->validatePasswords($this->password, $this->password2);
    }

    /**
     * Remove (chage status to removed) a user from system
     * @param INTEGER $userId
     * @throws Model_User_Exception on SQL-error
     */
    public function deleteUser($userId)
    {
        $db= self::getDatabaseHandler();
        try
        {
            $this->id = $userId;
            $this->userStatusId = self::USER_STATUS_REMOVED;

            $db->setRules($this->updateRules);
            $db->prepare('UPDATE User set userStatusId = {userStatusId} WHERE id = {id}');
            $db->setParameter('userStatusId', $this->userStatusId);
            $db->setParameter('id', $this->id);
            $db->execute();
        } catch (Model_User_Exception $e) {
            throw new Model_User_Exception('Unable to update user: ' . $e->getMessage());
        }
    }

}

class Model_User_Exception extends Core_Exception {

}